Informe análisis de datos German Credit Risk#
Análisis Exploratorio de Datos German Credit Risk#
import psycopg2
import pandas as pd
import kaleido
from sqlalchemy import create_engine
import numpy as np
import plotly.graph_objects as go
import sklearn.metrics
from sklearn.metrics import auc
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, confusion_matrix
from sklearn.model_selection import train_test_split
from scipy.stats import kstest
import statsmodels.api as sm
import statsmodels.graphics.tsaplots as tsaplots
import scipy.stats as stats
from sklearn.model_selection import train_test_split, GridSearchCV, KFold,validation_curve
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler,LabelEncoder,RobustScaler
from sklearn.compose import ColumnTransformer
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import classification_report, ConfusionMatrixDisplay,confusion_matrix
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
import matplotlib.pyplot as plt
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
Cell In[1], line 13
11 from sklearn.model_selection import train_test_split
12 from scipy.stats import kstest
---> 13 import statsmodels.api as sm
14 import statsmodels.graphics.tsaplots as tsaplots
15 import scipy.stats as stats
ModuleNotFoundError: No module named 'statsmodels'
connection = psycopg2.connect(database="pfinal_db", user="pfinal", password="password", host="127.0.0.1", port="5432")
cursor = connection.cursor()
Con el fragmento de código relacionado a continuación se crea tabla german_data_risk
cursor.execute('''DROP TABLE IF EXISTS german_data_risk''')
cursor.execute('''CREATE TABLE german_data_risk(
Status_of_existing_checking_account VARCHAR(3),
Duration_in_month INT,
Credit_history VARCHAR(3),
Purpose VARCHAR(3),
Credit_amount INT,
Savings_account_bonds VARCHAR(3),
Present_employment_since VARCHAR(3),
Installment_rate_in_percentage_of_disposable_income INT,
Personal_statu_and_sex VARCHAR(4),
Other_debtors_guarantors VARCHAR(4),
Present_residence_since INT,
Property VARCHAR(4),
Age_in_years INT,
Other_installment_plans VARCHAR(4),
Housing VARCHAR(4),
Number_of_existing_credits_at_this_bank INT,
Job VARCHAR(4),
Number_of_people_being_liable_to_provide_maintenance_for INT,
Telephone VARCHAR(4),
foreign_worker VARCHAR(4),
Class VARCHAR(1));''')
connection.commit()
df = pd.read_csv("C:/Users/kathy/Desktop/Visualizacion_cientifica/P_final/datos/german_credit_risk.csv", sep=';')
df['Class'] = df['Class'].astype(int)
df.head()
| Status_of_existing_checking_account | Duration_in_month | Credit_history | Purpose | Credit_amount | Savings_account_bonds | Present_employment_since | Installment_rate_in_percentage_of_disposable_income | Personal_statu_ and_sex | Other_debtors_guarantors | ... | Property | Age_in_years | Other_installment_plans | Housing | Number_of_existing_credits_at_this_bank | Job | Number_of_ people_being_liable_to_provide_maintenance_for | Telephone | foreign_worker | Class | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | A11 | 6 | A34 | A43 | 1169 | A65 | A75 | 4 | A93 | A101 | ... | A121 | 67 | A143 | A152 | 2 | A173 | 1 | A192 | A201 | 1 |
| 1 | A12 | 48 | A32 | A43 | 5951 | A61 | A73 | 2 | A92 | A101 | ... | A121 | 22 | A143 | A152 | 1 | A173 | 1 | A191 | A201 | 2 |
| 2 | A14 | 12 | A34 | A46 | 2096 | A61 | A74 | 2 | A93 | A101 | ... | A121 | 49 | A143 | A152 | 1 | A172 | 2 | A191 | A201 | 1 |
| 3 | A11 | 42 | A32 | A42 | 7882 | A61 | A74 | 2 | A93 | A103 | ... | A122 | 45 | A143 | A153 | 1 | A173 | 2 | A191 | A201 | 1 |
| 4 | A11 | 24 | A33 | A40 | 4870 | A61 | A73 | 3 | A93 | A101 | ... | A124 | 53 | A143 | A153 | 2 | A173 | 2 | A191 | A201 | 2 |
5 rows × 21 columns
Con el siguiente fragmento de código creo la conexión usando la librería sqlalchemy para cargar en PostgreSQL el archivo CSV que contiene la información y/o datos de GERMAN CREDIT RISK.
db_params = {
'host': 'localhost',
'database': 'pfinal_db',
'user': 'pfinal',
'password': 'password'
}
engine = create_engine(f'postgresql://{db_params["user"]}:{db_params["password"]}@{db_params["host"]}/{db_params["database"]}')
df.to_sql('german_data_risk', engine, if_exists = 'replace', index=False, method='multi')
connection.commit()
Descripción de los datos German Credit Risk.#
A continuación, se describe cada una las variables de la tabla german_data_risk.
Variable 1: (cualitativa) Estado de Cuenta Corriente
Código
Descripción
A11
0 DM
A12
Entre 0 y 200 DM
A13
Más de 200 DM
A14
No tiene cuenta corriente
Variable 2: (numérica) Duración en meses.
Variable 3: (cualitativa) Historia crediticia
Código
Descripción
A30
No se han tomado créditos / Todos los créditos reembolsados debidamente
A31
Todos los créditos en este banco reembolsados debidamente
A32
Créditos existentes reembolsados debidamente hasta ahora
A33
Retraso en el pago en el pasado
A34
Cuenta crítica / Otros créditos existentes (no en este banco)
Variable 4: (cualitativa) Propósito
Código
Descripción
A40
Carro (nuevo)
A41
Carro (usado)
A42
Equipamiento / Amoblado
A43
Radio / Televisión
A44
Usos domésticos
A45
Reparaciones
A46
Educación
A47
Vacaciones
A48
Reciclaje
A49
Negocios
A410
Otros
Variable 5: (numérica) Monto de crédito.
Variable 6: (cualitativa) Estado de Cuenta de Ahorro
Código
Descripción
A61
Menos de 100 DM
A62
Entre 100 y 500 DM
A63
Entre 500 y 1000 DM
A64
Más de 1000 DM
A65
Desconocido / No tiene cuenta de ahorros
Variable 7: (cualitativa) Actualmente empleado desde
Código
Descripción
A71
Desempleado
A72
Menos de 1 año
A73
Entre 1 y 4 años
A74
Entre 4 y 7 años
A75
Más de 7 años
Variable 8: (numérica) Tasa de pago a plazos en porcentaje del ingreso disponible.
Variable 9: (cualitativa) Estado civil y género
Código
Descripción
A91
Masculino – Divorciado o separado
A92
Femenino – Divorciada, separada o casada
A93
Masculino – Soltero
A94
Masculino – Casado / viudo
A95
Femenino – Soltera
Variable 10: (cualitativa) Otros deudores / garantes
Código
Descripción
A101
Ninguno
A102
Co solicitante
A103
Garante
Variable 11: (numérica) Residencia actual desde.
Variable 12: (cualitativa) Propiedad
Código
Descripción
A121
Bienes raíces
A122
NO Bienes raíces / Acuerdo de ahorro de sociedad de construcción / Seguro de vida
A123
NO Bienes raíces / Carro u otro (no incluido en variable 6)
A124
Desconocido / Sin propiedad
Variable 13: (numérica) Edad en años.
Variable 14: (cualitativa) Otros planes de pago
Código
Descripción
A141
Banco
A142
Tiendas
A143
Ninguno
Variable 15: (cualitativa) Tipo de Vivienda
Código
Descripción
A151
Rentado
A152
Propio
A153
Gratis
Variable 16: (numérica) Número de créditos existentes con este banco
Variable 17: (cualitativa) Estado Trabajo
Código
Descripción
A171
Desempleado / No calificado y no residente
A172
No calificado y residente
A173
Empleado calificado / Oficial
A174
Gestión/ Autónomo / Empleado / Funcionario altamente calificado
Variable 18: (numérica) Número de personas responsables de proporcionar mantenimiento.
Variable 19: (cualitativa) Resgistra Teléfono
Código
Descripción
A191
Ninguno
A192
Si, registrado bajo el nombre del usuario
Variable 20: (cualitativa) Trabajador extranjero
Código
Descripción
A201
Si
A202
No
Matriz de costo:
Este conjunto de datos requiere el uso de una matriz de costo, como se muestra a continuación:
(1 = Bueno, 2 = Malo)
1
2
1
0
1
2
5
0
Las filas representan la clasificación real y las columnas la clasificación predicha. Es peor clasificar a un cliente como bueno cuando es malo (5), que clasificar a un cliente como malo cuando es bueno (1).
A continuación, presentamos la descripción general del conjunto de datos german_data_risk.
print("Tipo de datos:")
print(df.dtypes)
total_registros = df.shape[0]
Tipo de datos:
Status_of_existing_checking_account object
Duration_in_month int64
Credit_history object
Purpose object
Credit_amount int64
Savings_account_bonds object
Present_employment_since object
Installment_rate_in_percentage_of_disposable_income int64
Personal_statu_ and_sex object
Other_debtors_guarantors object
Present_residence_since int64
Property object
Age_in_years int64
Other_installment_plans object
Housing object
Number_of_existing_credits_at_this_bank int64
Job object
Number_of_ people_being_liable_to_provide_maintenance_for int64
Telephone object
foreign_worker object
Class int32
dtype: object
cursor.execute('SELECT count(1) FROM german_data_risk;')
record = cursor.fetchone()
print(record)
(1000,)
Hemos observado que nuestro conjunto de datos almacenado en pfinal_db consta de 21 variables (columnas) que tienen tipos de datos float64 y object. El tipo de dato float64 incluye valores numéricos con decimales, mientras que el tipo object abarca variables que contienen información textual o mixta (texto y números) en un formato no numérico (datos categóricos). En total, tenemos 1000 registros en nuestro conjunto de datos.
De igual forma se observan espacios entre los nombres de nuestras variables, por tal razon procedemos a reenombrarlas con el siguiente fragmento de código:
df_r = df.rename({'Status_of_existing_checking_account': 'c_corriente',
'Duration_in_month': 'mes',
'Credit_history': 'h_crediticia',
'Purpose': 'proposito',
'Credit_amount': 'monto',
'Savings_account_bonds': 'c_ahorro',
'Present_employment_since': 'empleado_desde',
'Installment_rate_in_percentage_of_disposable_income': 'tasa_pago',
'Personal_statu_ and_sex': 'ecivil_genero',
'Other_debtors_guarantors': 'codeudores',
' Present_residence_since': 'residencia_desde',
'Property': 'propiedades',
'Age_in_years': 'edad',
'Other_installment_plans': 'otros_pagos',
'Housing': 'tipo_vivienda',
'Number_of_existing_credits_at_this_bank': 'creditos_activos',
'Job': 'trabajo',
'Number_of_ people_being_liable_to_provide_maintenance_for': 'personas_a_cargo',
'Telephone': 'tiene_telefono',
'foreign_worker': 'extranjero',
'Class': 'clasificacion',}, axis=1)
df_r.head()
| c_corriente | mes | h_crediticia | proposito | monto | c_ahorro | empleado_desde | tasa_pago | ecivil_genero | codeudores | ... | propiedades | edad | otros_pagos | tipo_vivienda | creditos_activos | trabajo | personas_a_cargo | tiene_telefono | extranjero | clasificacion | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | A11 | 6 | A34 | A43 | 1169 | A65 | A75 | 4 | A93 | A101 | ... | A121 | 67 | A143 | A152 | 2 | A173 | 1 | A192 | A201 | 1 |
| 1 | A12 | 48 | A32 | A43 | 5951 | A61 | A73 | 2 | A92 | A101 | ... | A121 | 22 | A143 | A152 | 1 | A173 | 1 | A191 | A201 | 2 |
| 2 | A14 | 12 | A34 | A46 | 2096 | A61 | A74 | 2 | A93 | A101 | ... | A121 | 49 | A143 | A152 | 1 | A172 | 2 | A191 | A201 | 1 |
| 3 | A11 | 42 | A32 | A42 | 7882 | A61 | A74 | 2 | A93 | A103 | ... | A122 | 45 | A143 | A153 | 1 | A173 | 2 | A191 | A201 | 1 |
| 4 | A11 | 24 | A33 | A40 | 4870 | A61 | A73 | 3 | A93 | A101 | ... | A124 | 53 | A143 | A153 | 2 | A173 | 2 | A191 | A201 | 2 |
5 rows × 21 columns
# Reemplazar '2' con una cadena vacía solo en la columna 'columna_especifica'
df_r['clasificacion'] = df_r['clasificacion'].replace(2, 0)
df_cleaned1['clasificacion']= df_cleaned1['clasificacion'].replace(2, 0)
print("Tipo de datos:")
print(df_r.dtypes)
total_registros = df_r.shape[0]
Tipo de datos:
c_corriente object
mes int64
h_crediticia object
proposito object
monto int64
c_ahorro object
empleado_desde object
tasa_pago int64
ecivil_genero object
codeudores object
residencia_desde int64
propiedades object
edad int64
otros_pagos object
tipo_vivienda object
creditos_activos int64
trabajo object
personas_a_cargo int64
tiene_telefono object
extranjero object
clasificacion int32
dtype: object
Resumen de los datos german_data_risk#
Variables númericas:#
df_r[[ "mes", "monto", "tasa_pago", "residencia_desde", "edad",
"creditos_activos", "personas_a_cargo"]].describe()
| mes | monto | tasa_pago | residencia_desde | edad | creditos_activos | personas_a_cargo | |
|---|---|---|---|---|---|---|---|
| count | 1000.000000 | 1000.000000 | 1000.000000 | 1000.000000 | 1000.000000 | 1000.000000 | 1000.000000 |
| mean | 20.903000 | 3271.258000 | 2.973000 | 2.845000 | 35.546000 | 1.407000 | 1.155000 |
| std | 12.058814 | 2822.736876 | 1.118715 | 1.103718 | 11.375469 | 0.577654 | 0.362086 |
| min | 4.000000 | 250.000000 | 1.000000 | 1.000000 | 19.000000 | 1.000000 | 1.000000 |
| 25% | 12.000000 | 1365.500000 | 2.000000 | 2.000000 | 27.000000 | 1.000000 | 1.000000 |
| 50% | 18.000000 | 2319.500000 | 3.000000 | 3.000000 | 33.000000 | 1.000000 | 1.000000 |
| 75% | 24.000000 | 3972.250000 | 4.000000 | 4.000000 | 42.000000 | 2.000000 | 1.000000 |
| max | 72.000000 | 18424.000000 | 4.000000 | 4.000000 | 75.000000 | 4.000000 | 2.000000 |
Basándonos en los resultados obtenidos, podemos inferir varios temas asociados con la distribución de los datos:
Montos de crédito: La distribución de los montos de crédito parece ser amplia, con un valor mínimo de 250 y un valor máximo de 18424. Esto indica que hay una variabilidad significativa en la cantidad de créditos otorgados.
Edad de los solicitantes: La edad de los solicitantes varía desde 19 hasta 75 años, con una media de aproximadamente 35 años. Esto sugiere que la población de solicitantes de crédito abarca una amplia gama de edades.
Tasa de pagos: La mayoría de los clientes parecen preferir tasas de pago entre 2 y 4, como lo indica el 25% en el primer cuartil, 50% en la mediana y 75% en el tercer cuartil.
Residencia desde: La mayoría de los clientes han estado residiendo en su ubicación actual durante al menos 2 años, como lo indican los cuartiles.
Número de créditos activos y personas a cargo: La mayoría de los clientes tienen uno o dos créditos activos y una o ninguna persona a cargo. Esto sugiere que la mayoría de los solicitantes no tienen una carga financiera extrema en términos de múltiples créditos activos o personas a cargo.
En general, estos resultados proporcionan una visión de la distribución y las características de la población de solicitantes de crédito, lo que puede ser útil para comprender mejor el perfil de los clientes y tomar decisiones informadas en la gestión de créditos.
Variables Categóricas:#
# Columnnas objeto de análisis
columns_to_analyze = ["c_corriente", "h_crediticia", "proposito",
"c_ahorro", "empleado_desde", "ecivil_genero",
"codeudores", "propiedades", "otros_pagos",
"tipo_vivienda", "trabajo", "tiene_telefono",
"extranjero", "clasificacion"]
# Crear un DataFrame vacío para almacenar los resultados de value_counts de cada columna
summary_df = pd.DataFrame(columns=['Variable', 'Valor', 'Frecuencia'])
# Iterar sobre cada columna y obtener sus frecuencias de valor
for col in columns_to_analyze:
col_value_counts = df_r[col].value_counts().reset_index()
col_value_counts.columns = ['Valor', 'Frecuencia']
col_value_counts['Variable'] = col
summary_df = pd.concat([summary_df, col_value_counts], ignore_index=True)
# Mostrar el resumen completo
print(summary_df)
Variable Valor Frecuencia
0 c_corriente A14 394
1 c_corriente A11 274
2 c_corriente A12 269
3 c_corriente A13 63
4 h_crediticia A32 530
5 h_crediticia A34 293
6 h_crediticia A33 88
7 h_crediticia A31 49
8 h_crediticia A30 40
9 proposito A43 280
10 proposito A40 234
11 proposito A42 181
12 proposito A41 103
13 proposito A49 97
14 proposito A46 50
15 proposito A45 22
16 proposito A44 12
17 proposito A410 12
18 proposito A48 9
19 c_ahorro A61 603
20 c_ahorro A65 183
21 c_ahorro A62 103
22 c_ahorro A63 63
23 c_ahorro A64 48
24 empleado_desde A73 339
25 empleado_desde A75 253
26 empleado_desde A74 174
27 empleado_desde A72 172
28 empleado_desde A71 62
29 ecivil_genero A93 548
30 ecivil_genero A92 310
31 ecivil_genero A94 92
32 ecivil_genero A91 50
33 codeudores A101 907
34 codeudores A103 52
35 codeudores A102 41
36 propiedades A123 332
37 propiedades A121 282
38 propiedades A122 232
39 propiedades A124 154
40 otros_pagos A143 814
41 otros_pagos A141 139
42 otros_pagos A142 47
43 tipo_vivienda A152 713
44 tipo_vivienda A151 179
45 tipo_vivienda A153 108
46 trabajo A173 630
47 trabajo A172 200
48 trabajo A174 148
49 trabajo A171 22
50 tiene_telefono A191 596
51 tiene_telefono A192 404
52 extranjero A201 963
53 extranjero A202 37
54 clasificacion 1 700
55 clasificacion 0 300
Procesamiento de Datos.#
Datos faltantes:#
A continuación, se realiza un análisis para identificar la presencia de datos faltantes en nuestro conjunto de datos. Para esto usamos el siguiente código:
pd.isna(df_r).sum()
c_corriente 0
mes 0
h_crediticia 0
proposito 0
monto 0
c_ahorro 0
empleado_desde 0
tasa_pago 0
ecivil_genero 0
codeudores 0
residencia_desde 0
propiedades 0
edad 0
otros_pagos 0
tipo_vivienda 0
creditos_activos 0
trabajo 0
personas_a_cargo 0
tiene_telefono 0
extranjero 0
clasificacion 0
dtype: int64
Detección y manejo de valores atípicos (outliers):#
A continuación, se realiza un análisis para identificar la presencia de outliers en nuestro conjunto de datos. Para esto usamos el método de detección de valores atípicos mediante las puntuaciones \( Z \), el cual establece el siguiente criterio: Cualquier dato cuya puntuación esté fuera de la tercera desviación estándar es un valor atípico.
import numpy as np
#Definición de función para detección de datos atípicos a través de ZScore
def outliers_zscore(data):
outliers = []
thres = 3
mean = np.mean(data)
std = np.std(data)
for i in data:
z_score = (i - mean)/std
if (np.abs(z_score) > thres):
outliers.append(i)
return outliers
from IPython.display import display, Markdown
display(Markdown('<center><img src="img/outlierszscores.png" alt="figure"></center>'))

La función outliers_zscore recorrer todos los datos y calcular la puntuación \( Z \) para cada punto de datos utilizando la fórmula \( \frac{(x_i - \mu)}{\sigma} \), donde \( x_i \) es cada valor de los datos, \( \mu \) es la media de los datos y \( \sigma \) es la desviación estándar de los datos. Luego, establece un umbral en la tercera desviación estandar e identifica como valores atípicos aquellos datos cuya puntuación \( Z \) (en valor absoluto) excede este umbral.
A continuación, se observan los datos atípicos obtenidos luego de aplicar el método descrito anteriormente.
df_n= df_r[[ "mes", "monto", "tasa_pago", "residencia_desde", "edad",
"creditos_activos", "personas_a_cargo"]]
mes_outliers = outliers_zscore(df_n.mes)
monto_outliers = outliers_zscore(df_n.monto)
tasa_pago_outliers = outliers_zscore(df_n.tasa_pago)
residencia_desde_outliers = outliers_zscore(df_n.residencia_desde)
edad_outliers = outliers_zscore(df_n.edad)
creditos_activos_outliers = outliers_zscore(df_n.creditos_activos)
personas_a_cargo_outliers = outliers_zscore(df_n.personas_a_cargo)
print("Outliers a través Método Z-scores para la variable Duración en meses: ", mes_outliers)
print("Outliers a través Método Z-scores para la variable Monto de crédito: ", monto_outliers)
print("Outliers a través Método Z-scores para la variable Tasa de pago a plazos en porcentaje del ingreso disponible.: ", tasa_pago_outliers)
print("Outliers a través Método Z-scores para la variable Residencia actual desde: ", residencia_desde_outliers)
print("Outliers a través Método Z-scores para la variable Edad en años: ", edad_outliers)
print("Outliers a través Método Z-scores para la variable Número de créditos existentes con este banco: ", creditos_activos_outliers)
print("Outliers a través Método Z-scores para la variable Número de personas a cargo: ", personas_a_cargo_outliers)
Outliers a través Método Z-scores para la variable Duración en meses: [60, 60, 60, 60, 60, 60, 60, 60, 60, 72, 60, 60, 60, 60]
Outliers a través Método Z-scores para la variable Monto de crédito: [12579, 14421, 12612, 15945, 11938, 14555, 12169, 11998, 13756, 14782, 14318, 12976, 11760, 12389, 12204, 15653, 14027, 14179, 12680, 15857, 11816, 15672, 18424, 14896, 12749]
Outliers a través Método Z-scores para la variable Tasa de pago a plazos en porcentaje del ingreso disponible.: []
Outliers a través Método Z-scores para la variable Residencia actual desde: []
Outliers a través Método Z-scores para la variable Edad en años: [70, 74, 75, 74, 75, 74, 74]
Outliers a través Método Z-scores para la variable Número de créditos existentes con este banco: [4, 4, 4, 4, 4, 4]
Outliers a través Método Z-scores para la variable Número de personas a cargo: []
Análisis Univariado#
Variables numéricas:#
A continuación, se presenta el fragmento de código correspondiente a la función que generá dos tipos diferentes de gráficos (Histograma y Diagrama de Cajas y Bigotes) para visualizar una variable unidimensional.
import plotly.figure_factory as ff
import pandas as pd
def double_plot(data, columna, descripcion):
fig = go.Figure()
fig = ff.create_distplot([data[columna].to_list()],[descripcion],show_rug=False)
fig.show()
A continuación se observa la distribución de nuestras variables objeto de análisis.
Duración en meses
df_r_b = df_n.copy()
bar_mes = double_plot(df_r_b,'mes','Mes')
En este histograma, podemos observar una distribución multimodal, indicada por la presencia de múltiples picos. Además, podemos notar un sesgo hacia la derecha, lo que sugiere la posible presencia de datos atípicos en el extremo superior de la distribución. La amplitud de la distribución también indica una variabilidad considerable en los datos.
Monto de crédito
df_r_b = df_n.copy()
bar_mes = double_plot(df_r_b,'monto','Monto')
En este histograma, podemos observar una distribución multimodal, indicada por la presencia de múltiples picos. Además, podemos notar un sesgo hacia la derecha, lo que sugiere la posible presencia de datos atípicos en el extremo superior de la distribución. La amplitud de la distribución también indica una variabilidad considerable en los datos.
Tasa de pago a plazos en porcentaje del ingreso disponible
df_r_b = df_n.copy()
bar_mes = double_plot(df_r_b,'tasa_pago','Tasa de Pago conforme a la capacidad de endeudamiento')
A pesar de tener una cantidad limitada de datos en el eje de las x, la gráfica exhibe una notable variabilidad en los datos, lo que sugiere una distribución multimodal en lugar de una distribución normal.
Residencia actual desde:
df_r_b = df_n.copy()
bar_mes = double_plot(df_r_b,'residencia_desde','Residencia actual desde')
Edad
df_r_b = df_n.copy()
bar_mes = double_plot(df_r_b,'edad','Edad')
La gráfica revela una distribución sesgada hacia la derecha, lo que indica que la mayoría de los clientes se concentran en el rango de edades entre 25 y 32 años. Además, se observa una tendencia consistente en la concesión de créditos, siendo este grupo de edad el receptor del mayor número de créditos. Este sesgo sugiere una preferencia o una mayor necesidad de servicios financieros en esta franja de edad específica.
Número de créditos existentes con este banco
df_r_b = df_n.copy()
bar_mes = double_plot(df_r_b,'creditos_activos','Número de créditos')
La gráfica indica que la mayoría de los clientes poseen un crédito existente con el banco, aunque el rango típico oscila entre 1 y 3 créditos. Sin embargo, los datos no siguen una distribución normal, lo que sugiere una variabilidad en el número de créditos mantenidos por los clientes, con una tendencia hacia la acumulación de un solo crédito.
Número de personas a cargo
df_r_b = df_n.copy()
bar_mes = double_plot(df_r_b,'personas_a_cargo','Número de Personas a Cargo')
La gráfica revela que la mayoría de los clientes tienen entre 1 y 2 personas a su cargo, siendo más frecuente aquellos con 1 persona a cargo.
De forma conjunta se pueden visualizar la distribución de cada una de nuestras variables númericas a través del siguiente gráfico de violín.
import matplotlib.pyplot as plt
import seaborn as sns
df_copy = df_r.copy()
fig, axes = plt.subplots(nrows=1, ncols=7, figsize=(20, 5))
sns.violinplot(data = df_copy, y= 'monto', ax=axes[0], color='lightblue')
axes[0].set(xlabel=None, ylabel= 'Monto')
sns.violinplot(data = df_copy, y= 'mes', ax=axes[1], color='#BC8F8F')
axes[1].set(xlabel=None, ylabel= 'Mes')
sns.violinplot(data = df_copy, y= 'tasa_pago', ax=axes[2], color='#DDA0DD')
axes[2].set(xlabel=None, ylabel= 'Tasa de pago')
sns.violinplot(data = df_copy, y= 'edad', ax=axes[3], color='#DB7093')
axes[3].set(xlabel=None, ylabel= 'Edad en años')
sns.violinplot(data = df_copy, y= 'creditos_activos', ax=axes[4], color='#B32781')
axes[4].set(xlabel=None, ylabel= 'Créditos activos')
sns.violinplot(data = df_copy, y= 'personas_a_cargo', ax=axes[5], color='#5012B3')
axes[5].set(xlabel=None, ylabel= 'Personas que tiene a cargo')
sns.violinplot(data = df_copy, y= 'residencia_desde', ax=axes[6], color='#410085')
axes[6].set(xlabel=None, ylabel= 'Residente desde (en meses)')
[Text(0.5, 0, ''), Text(0, 0.5, 'Residente desde (en meses)')]
Variables Categóricas#
A continuación, se presenta la distribución de cada una de las variables categóricas de nuestro conjunto de datos.
df_copy1 = df_r.copy()
fig, axes = plt.subplots(nrows=5, ncols=3, figsize=(18, 22))
sns.histplot(x = df_copy1['c_corriente'], kde= True, ax = axes[0,0], color = '#BC8F8F', stat = 'density')
axes[0,0].set(ylabel = None, xlabel = 'Propósito del crédito')
sns.histplot(x = df_copy1['proposito'], kde= True, ax = axes[0,1], color = '#DDA0DD', stat = 'density')
axes[0,1].set(ylabel = None, xlabel = 'Propósito del crédito')
sns.histplot(x = df_copy1['h_crediticia'], kde= True, ax = axes[0,2], color = '#DB7093', stat = 'density')
axes[0,2].set(ylabel = None, xlabel = 'Historial crediticio')
#fila 2
sns.histplot(x = df_copy1['c_ahorro'], kde= True, ax = axes[1,0], color = '#BC8F8F', stat = 'density')
axes[1,0].set(ylabel = None, xlabel = 'Cuentas de ahorro o bonos')
sns.histplot(x = df_copy1['empleado_desde'], kde= True, ax = axes[1,1], color = '#DDA0DD', stat = 'density')
axes[1,1].set(ylabel = None, xlabel = 'Antigüedad en empleo')
sns.histplot(x = df_copy1['ecivil_genero'], kde= True, ax = axes[1,2], color = '#DB7093', stat = 'density')
axes[1,2].set(ylabel = None, xlabel = 'Estado civil')
#fila 3
sns.histplot(x = df_copy1['codeudores'], kde= True, ax = axes[2,0], color = '#BC8F8F', stat = 'density')
axes[2,0].set(ylabel = None, xlabel = 'Codedudores')
sns.histplot(x = df_copy1['propiedades'], kde= True, ax = axes[2,1], color = '#DDA0DD', stat = 'density')
axes[2,1].set(ylabel = None, xlabel = 'Propiedades')
sns.histplot(x = df_copy1['otros_pagos'], kde= True, ax = axes[2,2], color = '#DB7093', stat = 'density')
axes[2,2].set(ylabel = None, xlabel = 'Otros pagos')
#fila 4
sns.histplot(x = df_copy1['tipo_vivienda'], kde= True, ax = axes[3,0], color = '#BC8F8F', stat = 'density')
axes[3,0].set(ylabel = None, xlabel = 'Tipo de vivienda')
sns.histplot(x = df_copy1['tiene_telefono'], kde= True, ax = axes[3,1], color = '#DDA0DD', stat = 'density')
axes[3,1].set(ylabel = None, xlabel = 'Registro de teléfono')
sns.histplot(x = df_copy1['trabajo'], kde= True, ax = axes[3,2], color = '#DB7093', stat = 'density')
axes[3,2].set(ylabel = None, xlabel = 'Trabajo')
#fila 5
sns.histplot(x = df_copy1['extranjero'], kde= True, ax = axes[4,0], color = '#BC8F8F', stat = 'density')
axes[4,0].set(ylabel = None, xlabel = 'Extranjería del cliente')
sns.histplot(x = df_copy1['clasificacion'], kde= True, ax = axes[4,1], color = '#BC8F8F', stat = 'density')
axes[4,1].set(ylabel = None, xlabel = 'Classificación')
[Text(0, 0.5, ''), Text(0.5, 0, 'Classificación')]
A continuación se graficara el comportamiento de las variable categórica con base a la clasificación de los clientes, para esto definimos la función figura_v_categoricas, la cual recibe como parámetros de entrada la columnas que no deseo gráficar, la categória que deseo gráficar y el color para cada una de las clasificaciones ( 1 - Buen Pagador, 0-Mal Pagador).
c_colors0 = ["#8B4513","#DEB887", "#FFEBCD","#C0C0C0", "#B0E0E6","#4682B4","#008080", "#B22222"]
c_colors1 = ["#8B4513","#DEB887", "#FFEBCD","#C0C0C0", "#B0E0E6","#4682B4","#008080", "#B22222"]
def figura_v_categoricas(categoria, color1, color0):
dfb = df_cleaned.copy()
dfb=dfb.loc[:, [categoria,'clasificacion']]
dfb['clasificacion'] = dfb['clasificacion'].astype(int)
dfb[categoria] = dfb[categoria].astype(str)
dfb=pd.DataFrame(dfb.value_counts().reset_index(name='conteo'))
figbar_c_history = go.Figure()
temporal1 = dfb[dfb['clasificacion']==1]
temporal1.loc['conteo'] = pd.Series(temporal1['clasificacion'].sum(), index=['clasificacion'])
temporal2 = dfb[dfb['clasificacion']==0]
figbar_c_history.add_trace(go.Bar(
x=temporal1[categoria],
y=temporal1['conteo'],
name='Buen pagador',
marker_color= color1,
opacity=1,
hovertemplate=None
))
figbar_c_history.add_trace(go.Bar(
x=temporal2[categoria],
y=temporal2['conteo'],
name='Mal pagador',
marker_color= color0,
opacity=1,
hovertemplate=None
))
figbar_c_history.update_layout(
xaxis_title="Clasificación",
yaxis_title="Frecuencia",
template="plotly_dark",
plot_bgcolor="rgba(0, 0, 0, 0)",
paper_bgcolor="rgba(0, 0, 0, 0)",
font_color="gray",
font_size=15,
hovermode="x unified"
)
return figbar_c_history
Gráfico de Historia Crediticia vs Clasificación de clientes.
fig = figura_v_categoricas('h_crediticia', c_colors0[1], c_colors1[0])
fig.show()
A través de la gráfica podemos ver la mayoria de clientes a la fecha han cumplido con sus obligaciones de pago hasta el momento de la recoleccion de los datos (A32), y los clientes clasificados como buenos pagadores superan considerablemente a los clientes clasificados como mal pagadores. Otro patrón similar se da para el caso de los clientes que tienen créditos en otros bancos (A34). Para el caso de los clientes que se retrasan en los pagos (A33) fueron clasificados como buenos pagadores y duplican a los clasificados como malos pagadores.
Gráfico de Estado de Cuenta Corriente vs Clasificación de clientes.
fig = figura_v_categoricas('c_corriente', c_colors0[4], c_colors1[5])
fig.show()
La gráfica revela que la mayoría de los clientes sin cuenta corriente (A14) son considerados buenos pagadores. Por otro lado, aquellos clientes que poseen cuenta corriente con la entidad financiera y tienen un saldo superior a 200 DM (A13) tienen un mayor número de buenos pagadores, aunque en comparación con los clientes con estados de cuenta diferentes, esta proporción es mínima
Gráfico de Estado de Cuenta Ahorros vs Clasificación de clientes.
fig = figura_v_categoricas('c_ahorro', c_colors0[1], c_colors1[7])
fig.show()
La gráfica muestra que el 50% de los clientes posee una cuenta de ahorro (A61), de los cuales el 56.6% fueron clasificados como buenos pagadores. Por otro lado, el 18% no cuenta con una cuenta de ahorro (A65), y el 82.5% de estos clientes fueron clasificados como buenos pagadores. El 32% restante de los clientes tiene entre 100 y 1000 DM en su cuenta de ahorros, y el 77.61% de este grupo total de clientes son clasificados como buenos pagadores.
Gráfico de tipo de Propiedade vs Clasificación de clientes.
fig = figura_v_categoricas('propiedades', c_colors0[2], c_colors1[5])
fig.show()
A traves del gráfico podemos observar que para todas las categórias la proporcion de clientes clasificados como buenos pagadores fue siempre mayor en comparacion con la proporcon de los clientes clasificados como malos pagadores.
Gráfico de tipo de vivienda vs Clasificación de clientes.
fig = figura_v_categoricas('tipo_vivienda', c_colors0[6], c_colors1[3])
fig.show()
El gráfico revela que el 71% de los clientes residen en viviendas propias (A152), de los cuales 527 están clasificados como buenos pagadores. Por otro lado, el 17.9% de los clientes viven en viviendas alquiladas (A151), mientras que el 11.1% vive en viviendas sin costo. Para estos dos últimos grupos, los clientes clasificados como buenos pagadores siempre superan en número a los clasificados como malos pagadores.
Análsis de Correlación#
A continuación se presenta análisis de correlación entre variables para evaluar la relación lineal entre pares de variables dentro del conjunto de datos a través de gráfico de dispersión.
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
df_r_c = df_n.copy()
sns.pairplot(df_r_c)
plt.show()
Análisis de correlación a través de mapa de calor.
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
df_r_c = df_n.copy()
plt.figure(figsize=(5,5))
corr=df_r_c[:].corr()
mask = np.triu(np.ones_like(corr, dtype=bool))
sns.heatmap(corr, cmap=sns.cubehelix_palette(as_cmap=True), mask=mask, square = True, annot = True)
plt.show()
Mátriz de correlación entre variables númericas.
print(df_r_c.corr())
mes monto tasa_pago residencia_desde edad \
mes 1.000000 0.624984 0.074749 0.034067 -0.036136
monto 0.624984 1.000000 -0.271316 0.028926 0.032716
tasa_pago 0.074749 -0.271316 1.000000 0.049302 0.058266
residencia_desde 0.034067 0.028926 0.049302 1.000000 0.266419
edad -0.036136 0.032716 0.058266 0.266419 1.000000
creditos_activos -0.011284 0.020795 0.021669 0.089625 0.149254
personas_a_cargo -0.023834 0.017142 -0.071207 0.042643 0.118201
creditos_activos personas_a_cargo
mes -0.011284 -0.023834
monto 0.020795 0.017142
tasa_pago 0.021669 -0.071207
residencia_desde 0.089625 0.042643
edad 0.149254 0.118201
creditos_activos 1.000000 0.109667
personas_a_cargo 0.109667 1.000000
A través del mapa de calor podemos observar gráfica y numéricamente, podemos observar que no hay una correlación significativa entre la mayoría de nuestras variables. En algunos casos, como la relación entre la edad y la residencia, la correlación es débil. Sin embargo, se identifica una correlación moderada entre el mes y la duración en meses de los créditos.
Análisis de asociación: Tablas cruzadas.
df_c= df_r[[ "c_corriente", "proposito", "h_crediticia", "c_ahorro", "empleado_desde",
"ecivil_genero", "codeudores", "propiedades", "otros_pagos", "tipo_vivienda", "tiene_telefono", "trabajo",
"extranjero", "clasificacion"]]
# Calcular tabla de frecuencias cruzadas con margen (margen en variable1)
tabla_cruzada_2 = pd.crosstab(df_c['c_corriente'], df_c['clasificacion'], margins=True)
# Calcular tabla de frecuencias cruzadas con porcentajes por fila
tabla_cruzada_3 = pd.crosstab(df_c['c_corriente'], df_c['clasificacion'], normalize='index')
print("\nTabla de Frecuencias Cruzadas entre Estado de cuentas de cheque existentes y clasificacion:")
print(tabla_cruzada_2)
print("\nTabla de Proporción entre Estado de cuentas de cheque existentes y clasificacion:")
print(tabla_cruzada_3)
Tabla de Frecuencias Cruzadas entre Estado de cuentas de cheque existentes y clasificacion:
clasificacion 0 1 All
c_corriente
A11 135 139 274
A12 105 164 269
A13 14 49 63
A14 46 348 394
All 300 700 1000
Tabla de Proporción entre Estado de cuentas de cheque existentes y clasificacion:
clasificacion 0 1
c_corriente
A11 0.492701 0.507299
A12 0.390335 0.609665
A13 0.222222 0.777778
A14 0.116751 0.883249
df_c= df_r[[ "c_corriente", "proposito", "h_crediticia", "c_ahorro", "empleado_desde",
"ecivil_genero", "codeudores", "propiedades", "otros_pagos", "tipo_vivienda", "tiene_telefono", "trabajo",
"extranjero", "clasificacion"]]
# Calcular tabla de frecuencias cruzadas con margen (margen en variable1)
tabla_cruzada_2 = pd.crosstab(df_c['proposito'], df_c['clasificacion'], margins=True)
# Calcular tabla de frecuencias cruzadas con porcentajes por fila
tabla_cruzada_3 = pd.crosstab(df_c['proposito'], df_c['clasificacion'], normalize='index')
print("\nTabla de Frecuencias Cruzadas entre Proposito y clasificacion:")
print(tabla_cruzada_2)
print("\nTabla de Proporción entre Proposito y clasificacion:")
print(tabla_cruzada_3)
Tabla de Frecuencias Cruzadas entre Proposito y clasificacion:
clasificacion 0 1 All
proposito
A40 89 145 234
A41 17 86 103
A410 5 7 12
A42 58 123 181
A43 62 218 280
A44 4 8 12
A45 8 14 22
A46 22 28 50
A48 1 8 9
A49 34 63 97
All 300 700 1000
Tabla de Proporción entre Proposito y clasificacion:
clasificacion 0 1
proposito
A40 0.380342 0.619658
A41 0.165049 0.834951
A410 0.416667 0.583333
A42 0.320442 0.679558
A43 0.221429 0.778571
A44 0.333333 0.666667
A45 0.363636 0.636364
A46 0.440000 0.560000
A48 0.111111 0.888889
A49 0.350515 0.649485
df_c= df_r[[ "c_corriente", "proposito", "h_crediticia", "c_ahorro", "empleado_desde",
"ecivil_genero", "codeudores", "propiedades", "otros_pagos", "tipo_vivienda", "tiene_telefono", "trabajo",
"extranjero", "clasificacion"]]
# Calcular tabla de frecuencias cruzadas con margen (margen en variable1)
tabla_cruzada_2 = pd.crosstab(df_c['h_crediticia'], df_c['clasificacion'], margins=True)
# Calcular tabla de frecuencias cruzadas con porcentajes por fila
tabla_cruzada_3 = pd.crosstab(df_c['h_crediticia'], df_c['clasificacion'], normalize='index')
print("\nTabla de Frecuencias Cruzadas entre Historia Crediticia y clasificacion:")
print(tabla_cruzada_2)
print("\nTabla de Proporción entre Historia Crediticia y clasificacion:")
print(tabla_cruzada_3)
Tabla de Frecuencias Cruzadas entre Historia Crediticia y clasificacion:
clasificacion 0 1 All
h_crediticia
A30 25 15 40
A31 28 21 49
A32 169 361 530
A33 28 60 88
A34 50 243 293
All 300 700 1000
Tabla de Proporción entre Historia Crediticia y clasificacion:
clasificacion 0 1
h_crediticia
A30 0.625000 0.375000
A31 0.571429 0.428571
A32 0.318868 0.681132
A33 0.318182 0.681818
A34 0.170648 0.829352
df_c= df_r[[ "c_corriente", "proposito", "h_crediticia", "c_ahorro", "empleado_desde",
"ecivil_genero", "codeudores", "propiedades", "otros_pagos", "tipo_vivienda", "tiene_telefono", "trabajo",
"extranjero", "clasificacion"]]
# Calcular tabla de frecuencias cruzadas con margen (margen en variable1)
tabla_cruzada_2 = pd.crosstab(df_c['c_ahorro'], df_c['clasificacion'], margins=True)
# Calcular tabla de frecuencias cruzadas con porcentajes por fila
tabla_cruzada_3 = pd.crosstab(df_c['c_ahorro'], df_c['clasificacion'], normalize='index')
print("\nTabla de Frecuencias Cruzadas entre Cuentas de Ahorro/Bono y clasificacion:")
print(tabla_cruzada_2)
print("\nTabla de Proporción entre Cuentas de Ahorro/Bono y clasificacion:")
print(tabla_cruzada_3)
Tabla de Frecuencias Cruzadas entre Cuentas de Ahorro/Bono y clasificacion:
clasificacion 0 1 All
c_ahorro
A61 217 386 603
A62 34 69 103
A63 11 52 63
A64 6 42 48
A65 32 151 183
All 300 700 1000
Tabla de Proporción entre Cuentas de Ahorro/Bono y clasificacion:
clasificacion 0 1
c_ahorro
A61 0.359867 0.640133
A62 0.330097 0.669903
A63 0.174603 0.825397
A64 0.125000 0.875000
A65 0.174863 0.825137
df_c= df_r[[ "c_corriente", "proposito", "h_crediticia", "c_ahorro", "empleado_desde",
"ecivil_genero", "codeudores", "propiedades", "otros_pagos", "tipo_vivienda", "tiene_telefono", "trabajo",
"extranjero", "clasificacion"]]
# Calcular tabla de frecuencias cruzadas con margen (margen en variable1)
tabla_cruzada_2 = pd.crosstab(df_c['empleado_desde'], df_c['clasificacion'], margins=True)
# Calcular tabla de frecuencias cruzadas con porcentajes por fila
tabla_cruzada_3 = pd.crosstab(df_c['empleado_desde'], df_c['clasificacion'], normalize='index')
print("\nTabla de Frecuencias Cruzadas entre Empleado desde y clasificacion:")
print(tabla_cruzada_2)
print("\nTabla de Proporción entre Empleado desde y clasificacion:")
print(tabla_cruzada_3)
Tabla de Frecuencias Cruzadas entre Empleado desde y clasificacion:
clasificacion 0 1 All
empleado_desde
A71 23 39 62
A72 70 102 172
A73 104 235 339
A74 39 135 174
A75 64 189 253
All 300 700 1000
Tabla de Proporción entre Empleado desde y clasificacion:
clasificacion 0 1
empleado_desde
A71 0.370968 0.629032
A72 0.406977 0.593023
A73 0.306785 0.693215
A74 0.224138 0.775862
A75 0.252964 0.747036
df_c= df_r[[ "c_corriente", "proposito", "h_crediticia", "c_ahorro", "empleado_desde",
"ecivil_genero", "codeudores", "propiedades", "otros_pagos", "tipo_vivienda", "tiene_telefono", "trabajo",
"extranjero", "clasificacion"]]
# Calcular tabla de frecuencias cruzadas con margen (margen en variable1)
tabla_cruzada_2 = pd.crosstab(df_c['ecivil_genero'], df_c['clasificacion'], margins=True)
# Calcular tabla de frecuencias cruzadas con porcentajes por fila
tabla_cruzada_3 = pd.crosstab(df_c['ecivil_genero'], df_c['clasificacion'], normalize='index')
print("\nTabla de Frecuencias Cruzadas entre Estado Civil - Género y clasificacion:")
print(tabla_cruzada_2)
print("\nTabla de Proporción entre Estado Civil - Género y clasificacion:")
print(tabla_cruzada_3)
Tabla de Frecuencias Cruzadas entre Estado Civil - Género y clasificacion:
clasificacion 0 1 All
ecivil_genero
A91 20 30 50
A92 109 201 310
A93 146 402 548
A94 25 67 92
All 300 700 1000
Tabla de Proporción entre Estado Civil - Género y clasificacion:
clasificacion 0 1
ecivil_genero
A91 0.400000 0.600000
A92 0.351613 0.648387
A93 0.266423 0.733577
A94 0.271739 0.728261
df_c= df_r[[ "c_corriente", "proposito", "h_crediticia", "c_ahorro", "empleado_desde",
"ecivil_genero", "codeudores", "propiedades", "otros_pagos", "tipo_vivienda", "tiene_telefono", "trabajo",
"extranjero", "clasificacion"]]
# Calcular tabla de frecuencias cruzadas con margen (margen en variable1)
tabla_cruzada_2 = pd.crosstab(df_c['codeudores'], df_c['clasificacion'], margins=True)
# Calcular tabla de frecuencias cruzadas con porcentajes por fila
tabla_cruzada_3 = pd.crosstab(df_c['codeudores'], df_c['clasificacion'], normalize='index')
print("\nTabla de Frecuencias Cruzadas entre Otros Deudores - Garantes y clasificacion:")
print(tabla_cruzada_2)
print("\nTabla de Proporción entre Otros Deudores - Garantes y clasificacion:")
print(tabla_cruzada_3)
Tabla de Frecuencias Cruzadas entre Otros Deudores - Garantes y clasificacion:
clasificacion 0 1 All
codeudores
A101 272 635 907
A102 18 23 41
A103 10 42 52
All 300 700 1000
Tabla de Proporción entre Otros Deudores - Garantes y clasificacion:
clasificacion 0 1
codeudores
A101 0.299890 0.700110
A102 0.439024 0.560976
A103 0.192308 0.807692
df_c= df_r[[ "c_corriente", "proposito", "h_crediticia", "c_ahorro", "empleado_desde",
"ecivil_genero", "codeudores", "propiedades", "otros_pagos", "tipo_vivienda", "tiene_telefono", "trabajo",
"extranjero", "clasificacion"]]
# Calcular tabla de frecuencias cruzadas con margen (margen en variable1)
tabla_cruzada_2 = pd.crosstab(df_c['propiedades'], df_c['clasificacion'], margins=True)
# Calcular tabla de frecuencias cruzadas con porcentajes por fila
tabla_cruzada_3 = pd.crosstab(df_c['propiedades'], df_c['clasificacion'], normalize='index')
print("\nTabla de Frecuencias Cruzadas entre Propiedad y clasificacion:")
print(tabla_cruzada_2)
print("\nTabla de Proporción entre Propiedad y clasificacion:")
print(tabla_cruzada_3)
Tabla de Frecuencias Cruzadas entre Propiedad y clasificacion:
clasificacion 0 1 All
propiedades
A121 60 222 282
A122 71 161 232
A123 102 230 332
A124 67 87 154
All 300 700 1000
Tabla de Proporción entre Propiedad y clasificacion:
clasificacion 0 1
propiedades
A121 0.212766 0.787234
A122 0.306034 0.693966
A123 0.307229 0.692771
A124 0.435065 0.564935
df_c= df_r[[ "c_corriente", "proposito", "h_crediticia", "c_ahorro", "empleado_desde",
"ecivil_genero", "codeudores", "propiedades", "otros_pagos", "tipo_vivienda", "tiene_telefono", "trabajo",
"extranjero", "clasificacion"]]
# Calcular tabla de frecuencias cruzadas con margen (margen en variable1)
tabla_cruzada_2 = pd.crosstab(df_c['otros_pagos'], df_c['clasificacion'], margins=True)
# Calcular tabla de frecuencias cruzadas con porcentajes por fila
tabla_cruzada_3 = pd.crosstab(df_c['otros_pagos'], df_c['clasificacion'], normalize='index')
print("\nTabla de Frecuencias Cruzadas entre Otros Planes de pago y clasificacion:")
print(tabla_cruzada_2)
print("\nTabla de Proporción entre Otros Planes de pago y clasificacion:")
print(tabla_cruzada_3)
Tabla de Frecuencias Cruzadas entre Otros Planes de pago y clasificacion:
clasificacion 0 1 All
otros_pagos
A141 57 82 139
A142 19 28 47
A143 224 590 814
All 300 700 1000
Tabla de Proporción entre Otros Planes de pago y clasificacion:
clasificacion 0 1
otros_pagos
A141 0.410072 0.589928
A142 0.404255 0.595745
A143 0.275184 0.724816
df_c= df_r[[ "c_corriente", "proposito", "h_crediticia", "c_ahorro", "empleado_desde",
"ecivil_genero", "codeudores", "propiedades", "otros_pagos", "tipo_vivienda", "tiene_telefono", "trabajo",
"extranjero", "clasificacion"]]
# Calcular tabla de frecuencias cruzadas con margen (margen en variable1)
tabla_cruzada_2 = pd.crosstab(df_c['tipo_vivienda'], df_c['clasificacion'], margins=True)
# Calcular tabla de frecuencias cruzadas con porcentajes por fila
tabla_cruzada_3 = pd.crosstab(df_c['tipo_vivienda'], df_c['clasificacion'], normalize='index')
print("\nTabla de Frecuencias Cruzadas entre Tipo de vivienda y clasificacion:")
print(tabla_cruzada_2)
print("\nTabla de Proporción entre Tipo de vivienda y clasificacion:")
print(tabla_cruzada_3)
Tabla de Frecuencias Cruzadas entre Tipo de vivienda y clasificacion:
clasificacion 0 1 All
tipo_vivienda
A151 70 109 179
A152 186 527 713
A153 44 64 108
All 300 700 1000
Tabla de Proporción entre Tipo de vivienda y clasificacion:
clasificacion 0 1
tipo_vivienda
A151 0.391061 0.608939
A152 0.260870 0.739130
A153 0.407407 0.592593
df_c= df_r[[ "c_corriente", "proposito", "h_crediticia", "c_ahorro", "empleado_desde",
"ecivil_genero", "codeudores", "propiedades", "otros_pagos", "tipo_vivienda", "tiene_telefono", "trabajo",
"extranjero", "clasificacion"]]
# Calcular tabla de frecuencias cruzadas con margen (margen en variable1)
tabla_cruzada_2 = pd.crosstab(df_c['tiene_telefono'], df_c['clasificacion'], margins=True)
# Calcular tabla de frecuencias cruzadas con porcentajes por fila
tabla_cruzada_3 = pd.crosstab(df_c['tiene_telefono'], df_c['clasificacion'], normalize='index')
print("\nTabla de Frecuencias Cruzadas entre Teléfono y clasificacion:")
print(tabla_cruzada_2)
print("\nTabla de Proporción entre Historia Teléfono y clasificacion:")
print(tabla_cruzada_3)
Tabla de Frecuencias Cruzadas entre Teléfono y clasificacion:
clasificacion 0 1 All
tiene_telefono
A191 187 409 596
A192 113 291 404
All 300 700 1000
Tabla de Proporción entre Historia Teléfono y clasificacion:
clasificacion 0 1
tiene_telefono
A191 0.313758 0.686242
A192 0.279703 0.720297
df_c= df_r[[ "c_corriente", "proposito", "h_crediticia", "c_ahorro", "empleado_desde",
"ecivil_genero", "codeudores", "propiedades", "otros_pagos", "tipo_vivienda", "tiene_telefono", "trabajo",
"extranjero", "clasificacion"]]
# Calcular tabla de frecuencias cruzadas con margen (margen en variable1)
tabla_cruzada_2 = pd.crosstab(df_c['trabajo'], df_c['clasificacion'], margins=True)
# Calcular tabla de frecuencias cruzadas con porcentajes por fila
tabla_cruzada_3 = pd.crosstab(df_c['trabajo'], df_c['clasificacion'], normalize='index')
print("\nTabla de Frecuencias Cruzadas entre Trabajo y clasificacion:")
print(tabla_cruzada_2)
print("\nTabla de Proporción entre Trabajo y clasificacion:")
print(tabla_cruzada_3)
Tabla de Frecuencias Cruzadas entre Trabajo y clasificacion:
clasificacion 0 1 All
trabajo
A171 7 15 22
A172 56 144 200
A173 186 444 630
A174 51 97 148
All 300 700 1000
Tabla de Proporción entre Trabajo y clasificacion:
clasificacion 0 1
trabajo
A171 0.318182 0.681818
A172 0.280000 0.720000
A173 0.295238 0.704762
A174 0.344595 0.655405
df_c= df_r[[ "c_corriente", "proposito", "h_crediticia", "c_ahorro", "empleado_desde",
"ecivil_genero", "codeudores", "propiedades", "otros_pagos", "tipo_vivienda", "tiene_telefono", "trabajo",
"extranjero", "clasificacion"]]
# Calcular tabla de frecuencias cruzadas con margen (margen en variable1)
tabla_cruzada_2 = pd.crosstab(df_c['extranjero'], df_c['clasificacion'], margins=True)
# Calcular tabla de frecuencias cruzadas con porcentajes por fila
tabla_cruzada_3 = pd.crosstab(df_c['extranjero'], df_c['clasificacion'], normalize='index')
print("\nTabla de Frecuencias Cruzadas entre Sí es recidente extranjero y clasificacion:")
print(tabla_cruzada_2)
print("\nTabla de Proporción entre Sí es recidente extranjero y clasificacion:")
print(tabla_cruzada_3)
Tabla de Frecuencias Cruzadas entre Sí es recidente extranjero y clasificacion:
clasificacion 0 1 All
extranjero
A201 296 667 963
A202 4 33 37
All 300 700 1000
Tabla de Proporción entre Sí es recidente extranjero y clasificacion:
clasificacion 0 1
extranjero
A201 0.307373 0.692627
A202 0.108108 0.891892
Resultado#
Aplicar un Análisis Exploratorio de Datos (EDA) sobre la base de datos German Credit Risk proporcionó una visión detallada y significativa de las características clave relacionadas con el riesgo crediticio. Durante el EDA, se examinaron minuciosamente variables como el historial crediticio, el propósito del crédito, la edad, el estado civil, el empleo y otros factores relevantes. Este proceso permitió identificar la distribución de estas variables, detectar posibles correlaciones entre ellas y comprender cómo influyen en las decisiones crediticias. Además, el EDA reveló patrones de comportamiento financiero y posibles anomalías o valores atípicos que se deben estudiar a profundidad.
Construcción de Modelos#
1 . Regresión Logística#
La regresión logísitica es un modelo que puede predecir la probabilidad que tiene una variable binaria (que puede aceptar 2 valores) de pertenecer a una clase o a otra. Es por tanto un método utilizado para la clasificación categórica de variables, especialmente útil por su simplicidad e interpretabilidad.
El modelo de regresión logística considera un conjunto de \(n\) observaciones indendientes de \(Y = (Y_1, Y_2, \ldots, Y_n)\) con los datos \(y_i \in \left\lbrace 0,1 \right\rbrace\) , para todo \(i=1,\ldots,n\) donde \(y_i\) es un posible valor de \(Y_i\), las cuales son independiente entre sí.
Dado lo anterior se llega a un modelo estadístico de Bernoulli:
Fijando las \(y = (y_1, \dots, y_n)^T\) obtenemos la ecuación de verosimilitud en el parámetro \(p=(p_1, \dots,p_n)^T\) como se muestra a continuación:
De lo anterior obtenemos el logaritmo de la función de verosimilitud de la siguiente forma:
como \(0\leq f(y,p) \leq 1\), de la expresión anterior se tiene que:
A continuación se define la formula general para el modelo de regresión logístico:
Donde \(\alpha = (\beta_1, \beta_2, \ldots, \beta_k)^T\) es el vector de parámetros en el modelo.
La ecuación para el riesgo \((p_j)\) estimado de este modelo esta dada bajo las siguientes condiciones:
Sea \(g_j:=\delta + \beta_1 x_{1j}+\beta_k x_{kj}\) , entonces la probabilidad \(p_j\) esta dada por \(p_j = P(Y_j = 1 \mid x_{1j}, \ldots , x{kj})\) de obtener un exito en la población \(j=1,2, \ldots, J\). Dado lo anterior la formula generar para estimar el riesgo es la que se muestra a continuación:
Modelo#
Preparación de datos: Dado que nuestro conjunto de datos incluye variables predictoras o explicativas categóricas con múltiples niveles, necesitamos realizar una transformación en nuestros datos originales utilizando el siguiente código. Esto es necesario ya que vamos a emplear la librería
sklearn.Las variables de interés para la construcción de nuestro modelo de clasificación se especifican como sigue:
c_corriente,c_ahorro,h_crediticia,propiedadesytipo_vivienda. Estas variables se seleccionaron debido a su relevancia teórica y su potencial capacidad para discriminar entre las categorías de clasificación objetivo
# una variable categórica en una nueva columna binaria y asignar un valor de 1 si la observación pertenece a esa categoría, y 0 en caso contrario.
datos_rl = df_r.copy()
datos_rl = pd.get_dummies(datos_rl,dtype=int)
datos_rl.head()
| mes | monto | tasa_pago | residencia_desde | edad | creditos_activos | personas_a_cargo | clasificacion | c_corriente_A11 | c_corriente_A12 | ... | tipo_vivienda_A152 | tipo_vivienda_A153 | trabajo_A171 | trabajo_A172 | trabajo_A173 | trabajo_A174 | tiene_telefono_A191 | tiene_telefono_A192 | extranjero_A201 | extranjero_A202 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 6 | 1169 | 4 | 4 | 67 | 2 | 1 | 1 | 1 | 0 | ... | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 0 |
| 1 | 48 | 5951 | 2 | 2 | 22 | 1 | 1 | 0 | 0 | 1 | ... | 1 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 |
| 2 | 12 | 2096 | 2 | 3 | 49 | 1 | 2 | 1 | 0 | 0 | ... | 1 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 1 | 0 |
| 3 | 42 | 7882 | 2 | 4 | 45 | 1 | 2 | 1 | 1 | 0 | ... | 0 | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 |
| 4 | 24 | 4870 | 3 | 4 | 53 | 2 | 2 | 0 | 1 | 0 | ... | 0 | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 |
5 rows × 62 columns
A continuación se configuran los parámetros de nuestro modelo de regresión:
Penalty: Especifica la norma utilizada en la penalización. En este caso, ‘l2’ indica que se utiliza la penalización L2, que es la norma Euclidiana al cuadrado.
Tol: Tolerancia para la detección de convergencia del algoritmo de optimización.
Class_weight: Peso asociado a las clases en el modelo. Si no se proporciona, todas las clases se asumen con el mismo peso.
Random_state: Semilla utilizada por el generador de números aleatorios para reproducibilidad.
Solver: Algoritmo a utilizar en el problema de optimización. En este caso,
lbfgsindica que se utiliza el método de Broyden–Fletcher–Goldfarb–Shanno (L-BFGS).Max_iter: Número máximo de iteraciones permitidas para la convergencia del algoritmo de optimización.
model1 = LogisticRegression(penalty='l2', tol=0.0001, random_state= 42, solver='lbfgs', max_iter=10000) #Definición de parámetros del modelo 1
A contnuación se ajusta el modelo a los datos de entrenamiento y se predicen las etiquetas de clase en el conjunto de prueba.
datosmod1 = datos_rl.copy() # Data cleaning: eliminar columnas no útiles
y = datosmod1['clasificacion'] # Extraer la variable dependiente (variable objetivo)
x = datosmod1.drop(columns=['clasificacion'])# Eliminar la columnas del DataFrame antes de aplicar One-Hot Encoding
x_train, x_test, y_train, y_test = train_test_split(x, y, random_state=42, test_size=0.30)# Dividir los datos en conjuntos de entrenamiento y prueba
mod1 = model1.fit(x_train, y_train) # Ajustar el modelo a los datos de entrenamiento
predictions1 = mod1.predict(x_test) # Predecir las etiquetas de clase en el conjunto de prueba
# Create lists
column_labels = x_train.columns.tolist() #Creo una lista con el nombre de las columnas
column_labels.append('Intercepto') #Adiciono a la lista la etiqueta intercepto.
coef = mod1.coef_.squeeze().tolist() #Se extraen los coeficientes del modelo en formato lista
coef.append(mod1.intercept_[0]) #Se adiciona a la lista de coficientes del modelo el valor del intercepto que arroja el modelo.
coef_model = pd.DataFrame([coef], columns=column_labels) #por ultimo convertimos la lista en un datafram indicando que la cabecera tome el nombre de column-labels
coef_model.head()
| mes | monto | tasa_pago | residencia_desde | edad | creditos_activos | personas_a_cargo | c_corriente_A11 | c_corriente_A12 | c_corriente_A13 | ... | tipo_vivienda_A153 | trabajo_A171 | trabajo_A172 | trabajo_A173 | trabajo_A174 | tiene_telefono_A191 | tiene_telefono_A192 | extranjero_A201 | extranjero_A202 | Intercepto | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | -0.020175 | -0.000119 | -0.331199 | -0.02651 | 0.028948 | -0.284983 | -0.198842 | -0.65294 | -0.371818 | 0.224311 | ... | -0.005754 | 0.233436 | -0.039806 | -0.082943 | -0.10733 | -0.13923 | 0.142587 | -0.523953 | 0.52731 | 2.989597 |
1 rows × 62 columns
from scipy.stats import norm
import statsmodels.api as sm
from sklearn.feature_selection import chi2
import pandas as pd
scores, pvalues = chi2(x_train, y_train)
com_dic = {'Feature': x_train.columns, 'p-value': pvalues}
df_pvalor = pd.DataFrame(com_dic)
print(df_pvalor)
Feature p-value
0 mes 2.191346e-41
1 monto 0.000000e+00
2 tasa_pago 4.193896e-01
3 residencia_desde 9.751762e-01
4 edad 5.574227e-12
.. ... ...
56 trabajo_A174 3.418935e-01
57 tiene_telefono_A191 5.367032e-01
58 tiene_telefono_A192 4.717353e-01
59 extranjero_A201 6.698647e-01
60 extranjero_A202 3.329495e-02
[61 rows x 2 columns]
['mes', 'monto', 'edad', 'c_corriente_A11', 'c_corriente_A12', 'c_corriente_A14', 'h_crediticia_A30', 'h_crediticia_A31', 'h_crediticia_A34', 'proposito_A40', 'proposito_A41', 'proposito_A43', 'proposito_A46', 'c_ahorro_A61', 'c_ahorro_A63', 'c_ahorro_A64', 'c_ahorro_A65', 'empleado_desde_A72', 'empleado_desde_A75', 'ecivil_genero_A92', 'propiedades_A121', 'propiedades_A124', 'tipo_vivienda_A151', 'tipo_vivienda_A152', 'tipo_vivienda_A153', 'extranjero_A202']
A continuación se describe la formula de nuestro modelo de regresión logístico, suponiendo que:
El cliente tiene un estado de cuenta corriente entre 0 y 200 DM(A11), No tiene cuenta de ahorros (A65), dentro de los créditos adquiridos con la entidad financiera todos han sido reembolsados debidamente (A31), cuenta con bienes raíces (A121) y cuenta con vivienda propia (A152)
Test de Normalidad de los residuales a través de Kolmogorov-Smirnov#
Planteamiento de Hipótesis:
Críterios de aceptación y/o rechazo:
Sí el P-value \(< 0.05 \), se rechaza la hipótesis nula con un a significancia \( \alpha = 0.05\), es decir que los datos no siguen una distribución normal.
Test de Independiencia de los residuales a través del Estadístico de Durbin Watson#
El estadístico de Durbin-Watson (DW) es una medida utilizada para evaluar la presencia de autocorrelación en los residuos de un modelo de regresión, éste toma valores entre 0 y 4. Un valor de DW cercano a 2 sugiere que no hay autocorrelación serial en los residuos. Esto significa que los residuos en un momento dado no están correlacionados con los residuos en momentos anteriores. Un valor de 2 implica la ausencia total de autocorrelación.Cuando el valor de DW se acerca a 0, indica autocorrelación positiva en los residuos. Esto significa que los residuos en un momento dado están correlacionados positivamente con los residuos en momentos anteriores. Por otro lado, si el valor de DW se acerca a 4, indica autocorrelación negativa en los residuos.
Análisis de residuales.#
# Calcular los residuos
ei_rl = y_test - predictions1
#Prueba de normalidad de Kolmogorov-Smirnov
from scipy.stats import kstest
ks_result = kstest(ei_rl, 'norm')
print("Prueba de Kolmogorov-Smirnov:")
print("Estadístico:", ks_result.statistic)
print("Valor p:", ks_result.pvalue)
Prueba de Kolmogorov-Smirnov:
Estadístico: 0.43000000000000005
Valor p: 6.666503380172174e-51
Dado que el P-value \(=4.3640e-50 < 0.05\) hay evidencia significativa para rechazar la hipótesis nula con una significancia \(\alpha = 0.05\), es decir que los residuos no siguen una distribución.normal
import statsmodels.api as sm
# Prueba de Durbin-Watson
durbin_watson_statistic = sm.stats.stattools.durbin_watson(ei_rl)
print("Estadístico de Durbin-Watson:", durbin_watson_statistic)
Estadístico de Durbin-Watson: 1.7647058823529411
Dado que el estadístico de Durbin_Watson \( = 1.911\), se puede afirmar que no existe una correlacion serial en los residuales.
import statsmodels.graphics.tsaplots as tsaplots
# Gráfico QQ-plot
fig, axs = plt.subplots(1, 1, figsize=(15, 5))
# QQ-plot para los residuales
stats.probplot(ei_rl, dist="norm", plot=axs)
axs.get_lines()[0].set_color('#008080') # Establecer el color del gráfico
axs.get_lines()[0].set_markersize(2) # Establecer el tamaño de los puntos
axs.set_title('QQ-plot para los residuales ordinarios', fontsize=16)
axs.tick_params(axis='x', labelsize=10) # Solo para el eje x (inferior)
axs.tick_params(axis='y', labelsize=10) # Solo para el eje y (izquierdo)
# Ajustar el espaciado entre los gráficos
plt.tight_layout()
# Mostrar la figura
plt.show()
Basándonos en el gráfico anterior, podemos concluir que, si bien los residuos no siguen estrictamente una distribución normal, sí se observa que cumplen con el supuesto de independencia. Esto se refiere a que los residuos no muestran patrones discernibles en su distribución, lo que sugiere que están aleatoriamente dispersos.
Métricas de evaluación de modelos#
Matriz de Confusión#
La matriz de confusión es una herramienta que permite visualizar el rendimiento de un modelo de clasificación al comparar las predicciones del modelo con las clases reales de los datos. Esta matriz organiza las predicciones en cuatro categorías: verdaderos positivos (TP), falsos positivos (FP), verdaderos negativos (TN) y falsos negativos (FN). Las entradas de la diagonal principal de la matriz de confusión corresponden a las clasificaciones correctas, mientras que las demás entradas nos indican cuántas muestras se han clasificado erróneamente en una clase.
from IPython.display import display, Markdown
display(Markdown('<center><img src="img/mconfusion.png" alt="figure"></center>'))

Métricas para modelos de Clasificación#
Sensibilidad (Precision): proporción de
predicciones verdaderas positivascon respecto al total depredicciones positivas. Permite detectar el error tipo I (Falsos Positivos).
Especificidad (Recall): proporción de
predicciones verdaderas positivascon respecto al total deobservaciones positivas. Permite detectar el error tipo II (Falsos Negativos).
F1 Score: El puntaje F1 es una medida que combina precision y recall en una sola métrica, proporcionando una evaluación global del rendimiento de un modelo de clasificación. Se calcula como la media armónica entre precision y recall.
$\(F = 2\cdot \frac{precision\cdot recall}{precision + recall}\)$ROC Curve (Receiver Operating Characteristic) : permite visualizar la habilidad de un modelo para distinguir entre dos clases al ajustar el umbral de decisión. Representa la sensibilidad (tasa de verdaderos positivos) frente a la especificidad (1 - tasa de falsos positivos). Cuanto más se acerque la curva al área bajo la curva (AUC) de 1, mejor será el modelo en diferenciar entre las clases. Es una herramienta crucial para evaluar y comparar modelos de clasificación binaria.
Función para estimación de Matriz de Confusión y Métricas para Modelos de Clasificación:
def metricas(x_test, y_test, y_hats, model):
mc = sklearn.metrics.confusion_matrix(y_test, y_hats) # Calcula matriz de confusión
precs = sklearn.metrics.precision_score(y_test, y_hats) # Calcula Indicador de presición
recall = sklearn.metrics.recall_score(y_test, y_hats) # Calcula indicador Recall
f1score = sklearn.metrics.f1_score(y_test, y_hats) # Calcula indicador F1 Score
# Visualizar matriz de confusión cuando se ejecute la función
vismc = sklearn.metrics.ConfusionMatrixDisplay(confusion_matrix=mc, display_labels=["Negativo", "Positivo"])
# Calculos para la visualizar la curva ROC
y_prob_pred = model.predict_proba(x_test)[:, 1] #Calcula la probabilidad de exito.
fpr, tpr, thresholds = sklearn.metrics.roc_curve(y_test, y_prob_pred) # Calcula los falsos positivos y falsos negativos para la curva ROC
return mc, precs, recall, f1score, vismc, fpr, tpr, y_prob_pred
Indexación de parámetros para la función metricas()
La siguiente función evalúa el rendimiento del modelo de clasificación después de entrenarlo con el 70% de las observaciones reales. Las comparaciones se realizan utilizando el 30% restante de las observaciones, a partir de las cuales se derivan las métricas de Precision, Recall y F1 Score. Además de estas métricas, la función proporciona el gráfico de la matriz de confusión correspondiente al modelo, basado en el marco de la regresión logística binaria. También extrae otros parámetros necesarios para la creación de la curva ROC, como los falsos positivos y los verdaderos positivos.
mc1, precs1, recall1, f1score1, vismc1, fpr, tpr, y_prob_pred= metricas(x_test, y_test, predictions1, mod1)
vismc1.plot(cmap=plt.cm.RdPu)
print('La proporción de predicciones verdaderas positivas con respecto al total de predicciones positivas es de: ', precs1)
print('La proporción de predicciones verdaderas positivas con respecto al total de observaciones positivas es de:', recall1)
print('El puntaje F1 es de:', f1score1)
La proporción de predicciones verdaderas positivas con respecto al total de predicciones positivas es de: 0.8
La proporción de predicciones verdaderas positivas con respecto al total de observaciones positivas es de: 0.8995215311004785
El puntaje F1 es de: 0.8468468468468469
Según el análisis del gráfico, observamos el siguiente comportamiento del modelo de regresión logístico (Clasificación):
De un total de 300 predicciones realizadas, 188 se clasificaron correctamente como verdaderas positivas. Esto significa que se identificaron adecuadamente los clientes considerados como buenos pagadores, quienes en realidad lo son.
Asimismo, de las 300 predicciones, 44 fueron clasificadas correctamente como verdaderas negativas. Esto indica que se identificaron correctamente los clientes catalogados como malos pagadores, quienes realmente lo son.
Sin embargo, se presentaron 21 falsos negativos entre las predicciones. Esto implica que algunos clientes que en realidad son buenos pagadores fueron erróneamente clasificados como malos pagadores.
Por otro lado, se registraron 47 falsos positivos. Este resultado indica que algunos clientes que son malos pagadores fueron incorrectamente clasificados como buenos pagadores.
A continuación se visualiza la curva ROC.
# Calcular el AUC
auc_value = auc(fpr, tpr)
# Crear la figura
fig_roc1 = go.Figure()
# Agregar el área bajo la curva ROC
fig_roc1.add_trace(go.Scatter(
x=fpr, y=tpr,
fill='tozeroy',
mode='lines',
line=dict(color='rgba(128,0,128,0.2)'),
name=f'ROC Curve (AUC={auc_value:.4f})'
))
# Agregar la línea de referencia diagonal
fig_roc1.add_shape(
type='line', line=dict(dash='dash'),
x0=0, x1=1, y0=0, y1=1
)
# Agregar etiqueta con el valor del AUC
fig_roc1.add_annotation(
xref="paper", yref="paper", # Coordenadas relativas al gráfico
x=0.95, y=0.05, # Coordenadas para la esquina inferior derecha
text=f'AUC: {auc_value:.4f}',
showarrow=False,
font=dict(size=12, color='black'), # Estilo del texto
bordercolor='black', # Color del borde del rectángulo
borderwidth=1, # Ancho del borde del rectángulo
bgcolor='rgba(255,255,255,0.7)' # Color de fondo del rectángulo con opacidad
)
# Actualizar las etiquetas y título
fig_roc1.update_layout(
title='ROC Curve',
xaxis_title='False Positive Rate',
yaxis_title='True Positive Rate',
width=700, height=500
)
fig_roc1.show()
A través del análisis de la curva ROC (Receiver Operating Characteristic), la cual constituye una herramienta gráfica utilizada para evaluar la capacidad de discriminación de un modelo entre clases, se ha determinado que el modelo en cuestión exhibe un valor de AUC (Área bajo la Curva) de 0.8166. Este resultado indica que el modelo tiene una probabilidad del 81.66% de clasificar correctamente una instancia aleatoria positiva por encima de una instancia aleatoria negativa.
Vamos a analizar la importancia de nuestras variables para determinar si al eliminar las no significativas podemos mejorar el valor del AUC en nuestro modelo de regresión logística, buscando así encontrar la configuración óptima A continuación se observa el listado de nuestras variables significativas. .
var_significativas = df_pvalor[df_pvalor["p-value"]<0.05]['Feature'].to_list()
print(var_significativas)
['mes', 'monto', 'edad', 'c_corriente_A11', 'c_corriente_A12', 'c_corriente_A14', 'h_crediticia_A30', 'h_crediticia_A31', 'h_crediticia_A34', 'proposito_A40', 'proposito_A41', 'proposito_A43', 'proposito_A46', 'c_ahorro_A61', 'c_ahorro_A63', 'c_ahorro_A64', 'c_ahorro_A65', 'empleado_desde_A72', 'empleado_desde_A75', 'ecivil_genero_A92', 'propiedades_A121', 'propiedades_A124', 'tipo_vivienda_A151', 'tipo_vivienda_A152', 'tipo_vivienda_A153', 'extranjero_A202']
A continuación se procede a eliminar las variables que no son significativas y realizo la transformación del nuevo conjunto de datos.
# Data cleaning: eliminar columnas no útiles
nonusefulcolumns = ["codeudores", "ecivil_genero", "residencia_desde", "otros_pagos","creditos_activos", "personas_a_cargo", "tiene_telefono", "trabajo" , "extranjero", "tasa_pago"]
df_cleaned1 = df_r.drop(columns=nonusefulcolumns,axis=0)
# Transformacion de datos model 2
df_cleaned1 = pd.get_dummies(df_cleaned1,dtype=int)
df_cleaned1.head()
| mes | monto | edad | clasificacion | c_corriente_A11 | c_corriente_A12 | c_corriente_A13 | c_corriente_A14 | h_crediticia_A30 | h_crediticia_A31 | ... | empleado_desde_A73 | empleado_desde_A74 | empleado_desde_A75 | propiedades_A121 | propiedades_A122 | propiedades_A123 | propiedades_A124 | tipo_vivienda_A151 | tipo_vivienda_A152 | tipo_vivienda_A153 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 6 | 1169 | 67 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 1 | 0 |
| 1 | 48 | 5951 | 22 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | ... | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 |
| 2 | 12 | 2096 | 49 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | ... | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 |
| 3 | 42 | 7882 | 45 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 |
| 4 | 24 | 4870 | 53 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | ... | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 |
5 rows × 40 columns
model2 = LogisticRegression(penalty='l2', tol=0.0001, random_state= 42, solver='lbfgs', max_iter=10000) #Definición de parámetros del modelo 1
A continuación se ajusta el modelo teniendo en cuenta solo las variables significativas a los datos de entrenamiento y se predicen las etiquetas de clase en el conjunto de prueba.
datosmod2 = df_cleaned1.copy() # Data cleaning: eliminar columnas no útiles
y = datosmod2['clasificacion'] # Extraer la variable dependiente (variable objetivo)
x = datosmod2.drop(columns=['clasificacion'])# Eliminar la columnas del DataFrame antes de aplicar One-Hot Encoding
x_train2, x_test2, y_train2, y_test2 = train_test_split(x, y, random_state=42, test_size=0.30)# Dividir los datos en conjuntos de entrenamiento y prueba
mod2 = model2.fit(x_train2, y_train2) # Ajustar el modelo a los datos de entrenamiento
predictions2 = mod2.predict(x_test2) # Predecir las etiquetas de clase en el conjunto de prueba
# Create lists
column_labels2 = x_train2.columns.tolist() #Creo una lista con el nombre de las columnas
column_labels2.append('Intercepto') #Adiciono a la lista la etiqueta intercepto.
coef2 = mod2.coef_.squeeze().tolist() #Se extraen los coeficientes del modelo en formato lista
coef2.append(mod2.intercept_[0]) #Se adiciona a la lista de coficientes del modelo el valor del intercepto que arroja el modelo.
coef_model2 = pd.DataFrame([coef2], columns=column_labels2) #por ultimo convertimos la lista en un datafram indicando que la cabecera tome el nombre de column-labels
coef_model2.head()
| mes | monto | edad | c_corriente_A11 | c_corriente_A12 | c_corriente_A13 | c_corriente_A14 | h_crediticia_A30 | h_crediticia_A31 | h_crediticia_A32 | ... | empleado_desde_A74 | empleado_desde_A75 | propiedades_A121 | propiedades_A122 | propiedades_A123 | propiedades_A124 | tipo_vivienda_A151 | tipo_vivienda_A152 | tipo_vivienda_A153 | Intercepto | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | -0.027623 | -0.000039 | 0.029473 | -0.676202 | -0.315655 | 0.244877 | 0.743465 | -0.546901 | -0.578196 | -0.001735 | ... | 0.304476 | 0.049319 | 0.410664 | -0.004676 | -0.029354 | -0.380149 | -0.129349 | 0.223751 | -0.097917 | 0.634517 |
1 rows × 40 columns
from scipy.stats import norm
import statsmodels.api as sm
from sklearn.feature_selection import chi2
import pandas as pd
scores2, pvalues2 = chi2(x_train2, y_train2)
com_dic2 = {'Feature': x_train2.columns, 'p-value': pvalues2}
df_pvalor2 = pd.DataFrame(com_dic2)
print(df_pvalor2)
var_significativas2 = df_pvalor2[df_pvalor2["p-value"]>0.05]['Feature'].to_list()
print(var_significativas2)
Feature p-value
0 mes 2.191346e-41
1 monto 0.000000e+00
2 edad 5.574227e-12
3 c_corriente_A11 4.081935e-08
4 c_corriente_A12 7.474776e-03
5 c_corriente_A13 1.729196e-01
6 c_corriente_A14 5.389366e-10
7 h_crediticia_A30 2.232779e-05
8 h_crediticia_A31 9.130752e-04
9 h_crediticia_A32 1.512213e-01
10 h_crediticia_A33 7.029163e-01
11 h_crediticia_A34 3.113208e-06
12 proposito_A40 5.428499e-03
13 proposito_A41 2.447812e-02
14 proposito_A410 1.639554e-01
15 proposito_A42 6.961868e-01
16 proposito_A43 2.986864e-03
17 proposito_A44 4.833771e-01
18 proposito_A45 9.679918e-01
19 proposito_A46 1.591644e-02
20 proposito_A48 3.679889e-01
21 proposito_A49 1.295529e-01
22 c_ahorro_A61 2.347905e-02
23 c_ahorro_A62 4.675334e-01
24 c_ahorro_A63 2.498426e-02
25 c_ahorro_A64 1.720144e-02
26 c_ahorro_A65 4.305769e-02
27 empleado_desde_A71 3.442791e-01
28 empleado_desde_A72 3.596840e-02
29 empleado_desde_A73 8.374780e-01
30 empleado_desde_A74 6.100268e-01
31 empleado_desde_A75 4.302258e-02
32 propiedades_A121 2.032207e-03
33 propiedades_A122 7.794099e-01
34 propiedades_A123 7.604576e-01
35 propiedades_A124 6.960475e-04
36 tipo_vivienda_A151 3.644128e-02
37 tipo_vivienda_A152 3.282593e-02
38 tipo_vivienda_A153 4.588936e-03
['c_corriente_A13', 'h_crediticia_A32', 'h_crediticia_A33', 'proposito_A410', 'proposito_A42', 'proposito_A44', 'proposito_A45', 'proposito_A48', 'proposito_A49', 'c_ahorro_A62', 'empleado_desde_A71', 'empleado_desde_A73', 'empleado_desde_A74', 'propiedades_A122', 'propiedades_A123']
A continuación se describe la formula de nuestro modelo de regresión logístico, suponiendo que:
El cliente tiene un estado de cuenta corriente entre 0 y 200 DM(A11), No tiene cuenta de ahorros (A65), dentro de los créditos adquiridos con la entidad financiera todos han sido reembolsados debidamente (A31), cuenta con bienes raíces (A121) y cuenta con vivienda propia (A152)
Test de Normalidad de los residuales a través de Kolmogorov-Smirnov#
Planteamiento de Hipótesis:
Críterios de aceptación y/o rechazo:
Sí el P-value \(< 0.05 \), se rechaza la hipótesis nula con un a significancia \( \alpha = 0.05\), es decir que los datos no siguen una distribución normal.
Test de Independiencia de los residuales a través del Estadístico de Durbin Watson#
El estadístico de Durbin-Watson (DW) es una medida utilizada para evaluar la presencia de autocorrelación en los residuos de un modelo de regresión, éste toma valores entre 0 y 4. Un valor de DW cercano a 2 sugiere que no hay autocorrelación serial en los residuos. Esto significa que los residuos en un momento dado no están correlacionados con los residuos en momentos anteriores. Un valor de 2 implica la ausencia total de autocorrelación.Cuando el valor de DW se acerca a 0, indica autocorrelación positiva en los residuos. Esto significa que los residuos en un momento dado están correlacionados positivamente con los residuos en momentos anteriores. Por otro lado, si el valor de DW se acerca a 4, indica autocorrelación negativa en los residuos.
Análisis de residuales.#
# Calcular los residuos
ei_rl2 = y_test2 - predictions2
#Prueba de normalidad de Kolmogorov-Smirnov
from scipy.stats import kstest
ks_result2 = kstest(ei_rl2, 'norm')
print("Prueba de Kolmogorov-Smirnov:")
print("Estadístico:", ks_result.statistic)
print("Valor p:", ks_result.pvalue)
Prueba de Kolmogorov-Smirnov:
Estadístico: 0.43000000000000005
Valor p: 6.666503380172174e-51
Dado que el P-value \(=4.3640e-50 < 0.05\) hay evidencia significativa para rechazar la hipótesis nula con una significancia \(\alpha = 0.05\), es decir que los residuos no siguen una distribución.normal
import statsmodels.api as sm
# Prueba de Durbin-Watson
durbin_watson_statistic2 = sm.stats.stattools.durbin_watson(ei_rl2)
print("Estadístico de Durbin-Watson:", durbin_watson_statistic2)
Estadístico de Durbin-Watson: 1.7875
Dado que el estadístico de Durbin_Watson \( = 1.911\), se puede afirmar que no existe una correlacion serial en los residuales.
import statsmodels.graphics.tsaplots as tsaplots
# Gráfico QQ-plot
fig, axs = plt.subplots(1, 1, figsize=(15, 5))
# QQ-plot para los residuales
stats.probplot(ei_rl2, dist="norm", plot=axs)
axs.get_lines()[0].set_color('#008080') # Establecer el color del gráfico
axs.get_lines()[0].set_markersize(2) # Establecer el tamaño de los puntos
axs.set_title('QQ-plot para los residuales ordinarios', fontsize=16)
axs.tick_params(axis='x', labelsize=10) # Solo para el eje x (inferior)
axs.tick_params(axis='y', labelsize=10) # Solo para el eje y (izquierdo)
# Ajustar el espaciado entre los gráficos
plt.tight_layout()
# Mostrar la figura
plt.show()
Basándonos en el gráfico anterior, podemos concluir que, si bien los residuos no siguen estrictamente una distribución normal, sí se observa que cumplen con el supuesto de independencia. Esto se refiere a que los residuos no muestran patrones discernibles en su distribución, lo que sugiere que están aleatoriamente dispersos.
Métricas de evaluación de modelos#
Indexación de parámetros para la función metricas()
La siguiente función evalúa el rendimiento del modelo de clasificación después de entrenarlo con el 70% de las observaciones reales. Las comparaciones se realizan utilizando el 30% restante de las observaciones, a partir de las cuales se derivan las métricas de Precision, Recall y F1 Score. Además de estas métricas, la función proporciona el gráfico de la matriz de confusión correspondiente al modelo, basado en el marco de la regresión logística binaria. También extrae otros parámetros necesarios para la creación de la curva ROC, como los falsos positivos y los verdaderos positivos.
mc2, precs2, recall2, f1score2, vismc2, fpr2, tpr2, y_prob_pred2= metricas(x_test2, y_test2, predictions2, mod2)
vismc2.plot(cmap=plt.cm.RdPu)
print('La proporción de predicciones verdaderas positivas con respecto al total de predicciones positivas es de: ', precs2)
print('La proporción de predicciones verdaderas positivas con respecto al total de observaciones positivas es de:', recall2)
print('El puntaje F1 es de:', f1score2)
La proporción de predicciones verdaderas positivas con respecto al total de predicciones positivas es de: 0.7676348547717843
La proporción de predicciones verdaderas positivas con respecto al total de observaciones positivas es de: 0.8851674641148325
El puntaje F1 es de: 0.8222222222222222
Según el análisis del gráfico, observamos el siguiente comportamiento del modelo de regresión logístico (Clasificación):
De un total de 300 predicciones realizadas, 188 se clasificaron correctamente como verdaderas positivas. Esto significa que se identificaron adecuadamente los clientes considerados como buenos pagadores, quienes en realidad lo son.
Asimismo, de las 300 predicciones, 44 fueron clasificadas correctamente como verdaderas negativas. Esto indica que se identificaron correctamente los clientes catalogados como malos pagadores, quienes realmente lo son.
Sin embargo, se presentaron 21 falsos negativos entre las predicciones. Esto implica que algunos clientes que en realidad son buenos pagadores fueron erróneamente clasificados como malos pagadores.
Por otro lado, se registraron 47 falsos positivos. Este resultado indica que algunos clientes que son malos pagadores fueron incorrectamente clasificados como buenos pagadores.
A continuación se visualiza la curva ROC.
# Calcular el AUC
auc_value2 = auc(fpr2, tpr2)
# Crear la figura
fig_roc2 = go.Figure()
# Agregar el área bajo la curva ROC
fig_roc2.add_trace(go.Scatter(
x=fpr2, y=tpr2,
fill='tozeroy',
mode='lines',
line=dict(color='rgba(128,0,128,0.2)'),
name=f'ROC Curve (AUC={auc_value2:.4f})'
))
# Agregar la línea de referencia diagonal
fig_roc2.add_shape(
type='line', line=dict(dash='dash'),
x0=0, x1=1, y0=0, y1=1
)
# Agregar etiqueta con el valor del AUC
fig_roc2.add_annotation(
xref="paper", yref="paper", # Coordenadas relativas al gráfico
x=0.95, y=0.05, # Coordenadas para la esquina inferior derecha
text=f'AUC: {auc_value2:.4f}',
showarrow=False,
font=dict(size=12, color='black'), # Estilo del texto
bordercolor='black', # Color del borde del rectángulo
borderwidth=1, # Ancho del borde del rectángulo
bgcolor='rgba(255,255,255,0.7)' # Color de fondo del rectángulo con opacidad
)
# Actualizar las etiquetas y título
fig_roc2.update_layout(
title='ROC Curve modelo 2',
xaxis_title='False Positive Rate',
yaxis_title='True Positive Rate',
width=700, height=500
)
fig_roc2.show()
A través del análisis de la curva ROC (Receiver Operating Characteristic), la cual constituye una herramienta gráfica utilizada para evaluar la capacidad de discriminación de un modelo entre clases, se ha determinado que el modelo en cuestión exhibe un valor de AUC (Área bajo la Curva) de 78.94. Este resultado indica que el modelo tiene una probabilidad del 78,94 % de clasificar correctamente una instancia aleatoria positiva por encima de una instancia aleatoria negativa.
2. KNN (K vecinos más próximos)#
El algoritmo del vecino más cercano se destaca por su simplicidad dentro del campo del aprendizaje automático. Su enfoque se basa en memorizar el conjunto de entrenamiento y luego predecir la etiqueta del vecino más cercano en dicho conjunto. Este método se fundamenta en la premisa de que las características utilizadas para describir los puntos en el dominio son determinantes para sus etiquetas; por lo tanto, es probable que puntos cercanos compartan la misma etiqueta.
A diferencia de otros enfoques algorítmicos que requieren hipótesis predefinidas, el método del vecino más cercano calcula una etiqueta para cualquier punto de prueba sin necesidad de buscar un predictor dentro de una clase de funciones establecida de antemano.Lihki Rubio )
Modelo#
Las variables de interés para la construcción de nuestro modelo de clasificación se especifican como sigue:
c_corriente,c_ahorro,h_crediticia,propiedadesytipo_vivienda. Estas variables se seleccionaron debido a su relevancia teórica y su potencial capacidad para discriminar entre las categorías de clasificación objetivo
datosmod2 = df_r.copy() # Data cleaning: eliminar columnas no útiles
datosmod2 = pd.get_dummies(datosmod2,dtype=int)
print(datosmod2.dtypes)
mes int64
monto int64
tasa_pago int64
residencia_desde int64
edad int64
...
trabajo_A174 int32
tiene_telefono_A191 int32
tiene_telefono_A192 int32
extranjero_A201 int32
extranjero_A202 int32
Length: 62, dtype: object
A continuación realizamos la separación de nuestro conjunto de datos para obtener los datos de entrenamieno (70%) y los datos de test (30%).
train, test = train_test_split(datosmod2, test_size=0.3, random_state=77)
Luego de realizar el paso anterior procedemos a separar los datos.
x_train = train.drop(['clasificacion'],axis=1)
y_train = train['clasificacion']
x_test = test.drop(['clasificacion'],axis=1)
y_test = test['clasificacion']
El siguiente fragmento de código utiliza la biblioteca scikit-learn para crear un proceso de preprocesamiento de datos que escala las características numéricas utilizando StandardScaler.
Comienza importando StandardScaler de scikit-learn.preprocessing, un transformador que ajusta cada característica numérica para que tenga una media de 0 y una desviación estándar de 1. Luego, se importa ColumnTransformer de scikit-learn.compose, que permite aplicar transformaciones específicas a diferentes columnas de un conjunto de datos.
A continuación, se crea un objeto scaler de StandardScaler, el cual será utilizado para escalar las características numéricas. Posteriormente, se define un ColumnTransformer llamado transformer, que especifica que todas las características numéricas deben ser escaladas utilizando el scaler. Esto se logra mediante el argumento transformers, el cual toma una lista de tuplas, donde cada tupla contiene el nombre de la transformación y el transformador correspondiente. En este caso, el nombre es num y el transformador es scaler.
Finalmente, se define una lista llamada steps_knn, que contiene una tupla con el nombre scale y el transformador scaler. Esta lista se utilizará en un pipeline de scikit-learn para aplicar el escalado de características antes de entrenar un modelo, en este caso, de vecinos más cercanos (KNN).
En resumen, este código establece un proceso de preprocesamiento de datos que escala las características numéricas utilizando StandardScaler, como parte fundamental de la preparación de los datos para el modelo de aprendizaje automático.
scaler = StandardScaler()
steps_knn = [
("scale", scaler)
]
Un pipeline en el contexto de aprendizaje automático es una secuencia de transformaciones de datos seguidas por la aplicación de un modelo predictivo. Esto permite que todo el proceso de preprocesamiento de datos y modelado se realice de manera ordenada y sistemática.
A continuación, se crea un pipeline de scikit-learn que puede ser usado para aplicar secuencialmente el preprocesamiento de datos y luego entrenar nuestro modelo de vecinos más cercanos (KNN) en el conjunto de datos objeto de análisis. Esto proporciona una forma conveniente de encapsular todo el flujo de trabajo de modelado en una sola entidad.
pipeline_knn = Pipeline(steps_knn)
Transformacion de los datos y definición del rango para el valor de k
x_train_knn = pipeline_knn.fit_transform(x_train)
k_values = np.linspace(1, 20, 20, dtype='int64')
A continuación, identificados por medio de una curva de validación cual es el mejor k para evaluar que tan bien el modelo predice, La función validation_curve realizará la validación cruzada para cada valor en param_range y devolvuelve las puntuaciones de entrenamiento y validación para cada valor de parámetro. Estas puntuaciones se pueden utilizar para analizar cómo varía el rendimiento del modelo con diferentes valores de hiperparámetro y seleccionar el mejor valor para optimizar el rendimiento del modelo.
train_scores, val_scores = validation_curve(estimator=KNeighborsClassifier(),
X=x_train_knn,
y=y_train,
param_name='n_neighbors',
param_range=k_values,
scoring='accuracy',
cv=10)
Se extraen las metricas descriptivas
train_mean = np.mean(train_scores, axis=1)
train_std = np.std(train_scores, axis=1)
val_mean = np.mean(val_scores, axis=1)
val_std = np.std(val_scores, axis=1)
import plotly.graph_objects as go
fig = go.Figure()
fig.add_trace(go.Scatter(x=k_values, y=train_mean+train_std,mode='lines',line_color="red",showlegend=False))
fig.add_trace(go.Scatter(x=k_values, y=train_mean,mode='lines+markers',name='Exactitud Entrenamiento',line_color="red",fill='tonexty'))
fig.add_trace(go.Scatter(x=k_values, y=train_mean-train_std,mode='lines',line_color="red",fill='tonexty',showlegend=False))
fig.add_trace(go.Scatter(x=k_values, y=val_mean+train_std,mode='lines',line_color="green",showlegend=False))
fig.add_trace(go.Scatter(x=k_values, y=val_mean,mode='lines+markers',name='Exactitud Test',line_color="green",fill='tonexty'))
fig.add_trace(go.Scatter(x=k_values, y=val_mean-train_std,mode='lines',line_color="green",fill='tonexty',showlegend=False))
fig.update_layout(title="Exactitud modelo",xaxis_title="K",yaxis_title="Exactitud",legend=dict(yanchor="bottom",xanchor="right",y=0.1))
fig.show()
mejor_indice = np.argmax(val_mean)
mejor_k = k_values[mejor_indice]
print(mejor_k)
10
una vez identificamos el mejor k, en este caso \(k = 10\), entrenamos nuevammente el modelo
mejor_modelo_knn = KNeighborsClassifier(n_neighbors=mejor_k).fit(x_train_knn, y_train)
x_test_knn = pipeline_knn.fit_transform(x_test)
A continuación se realiza la predicción del modelo
y_pred_knn = mejor_modelo_knn.predict(x_test_knn)
Se genera la matriz de confusión
cm_knn = confusion_matrix(y_test, y_pred_knn, labels=mejor_modelo_knn.classes_)
disp = ConfusionMatrixDisplay(confusion_matrix=cm_knn,display_labels=mejor_modelo_knn.classes_)
disp.plot()
<sklearn.metrics._plot.confusion_matrix.ConfusionMatrixDisplay at 0x227b0e0c1c0>
con el siguiente codigo generamos las metricas de evaluación
accuracy_knn = accuracy_score(y_test, y_pred_knn)
precision_knn_0 = precision_score(y_test, y_pred_knn,pos_label=0)
precision_knn_1 = precision_score(y_test, y_pred_knn,pos_label=1)
recall_knn_0 = recall_score(y_test, y_pred_knn,pos_label=0)
recall_knn_1 = recall_score(y_test, y_pred_knn,pos_label=1)
f1_knn_0 = f1_score(y_test, y_pred_knn,pos_label=0)
f1_knn_1 = f1_score(y_test, y_pred_knn,pos_label=1)
dato_comparativo_knn = {
'|':['|','|'],
'Modelo':['K-Vecinos',''],
'Clase':[0,1],
'Accuracy':[accuracy_knn,accuracy_knn],
'Precision':[precision_knn_0,precision_knn_1],
'Recall':[recall_knn_0,recall_knn_1],
'F1 Score':[f1_knn_0,f1_knn_1]}
resultado_knn= pd.DataFrame(dato_comparativo_knn)
print(resultado_knn)
| Modelo Clase Accuracy Precision Recall F1 Score
0 | K-Vecinos 0 0.746667 0.604167 0.337209 0.432836
1 | 1 0.746667 0.773810 0.911215 0.836910